#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "options.h"

#define OPT_LONG_NUM_SAMPLES      "--num_samples="
#define OPT_SHORT_NUM_SAMPLES     "-n"
#define OPT_DEFAULT_NUM_SAMPLES   10000

#define OPT_LONG_BURNIN           "--burn_in="
#define OPT_SHORT_BURNIN          "-b"
#define OPT_DEFAULT_BURNIN        0

#define OPT_LONG_INTERVAL         "--interval="
#define OPT_SHORT_INTERVAL        "-i"
#define OPT_DEFAULT_INTERVAL      -1111

#define OPT_LONG_RANDOMIZE        "--randomize"
#define OPT_SHORT_RANDOMIZE       "-r"
#define OPT_DEFAULT_RANDOMIZE     0

#define OPT_LONG_VERBOSE          "--verbose"
#define OPT_SHORT_VERBOSE         "-v"
#define OPT_DEFAULT_VERBOSE       0

#define OPT_LONG_HELP             "--help"
#define OPT_SHORT_HELP            "-h"
#define OPT_DEFAULT_HELP          0

#define OPT_LONG_LASTVAL          "--lastval"
#define OPT_SHORT_LASTVAL         "-l"
#define OPT_DEFAULT_LASTVAL       0

/*
  Adapted from Brian Milch's Java BLOG Class Main. Added --lastval.
*/

const char options_usage[] =
"Options:\n"
"-n num, --num_samples=num\n"
"    Run the sampling engine for num samples. Default: 10000 samples.\n"
"-b num, --burn_in=num\n"
"    Treat first num samples as burn-in period (don't use them to compute\n"
"    query results). Default: 0.\n"
"-i, --interval\n"
"    Specify the interval at which output is written. Note that if -i and -n\n"
"    are the same, output is written only once, at the last iteration.\n"
"    (NOTE: this is slightly modified from Brian Milch's semantics. -i is\n"
"     respected even without a write file option (-w)). Default 500.\n"
"-l, --lastval\n"
"    Display the last value of a query instead of the average. This can be\n"
"    used in conjunction with -i 1 to generate trace plots. Note: the last\n"
"    value is fixed to null during the burn-in samples.\n"
"-r, --randomize\n"
"    Initialize the random seed based on the clock time. If this flag is not\n"
"    given, the program uses a fixed random seed so its behavior is\n"
"    reproducible. Default: false \n"
"-h  --help\n"
"    Print this usage message and exit\n";

typedef struct optarg
{
  int argc_optarg;
  char ** argv_optarg;
} optarg;

static optarg * optarg_alloc(int argc, char ** argv)
{
  optarg * arg;
  
  /* allocate and initialize, skipping over program name */
  arg = (optarg *)malloc(sizeof(*arg));
  arg->argc_optarg = argc - 1;
  arg->argv_optarg = argv + 1;
  
  return arg;
}

static int optarg_more(optarg * arg)
{
  return arg->argc_optarg > 0;
}

/* returns 0 on successful match */
static int optarg_long(optarg * arg, long * pval, char * long_name,
                       char * short_name)
{
  if (!strncmp(*arg->argv_optarg, long_name, strlen(long_name)))
  {
    *pval = atol((*arg->argv_optarg) + strlen(long_name));
  }
  else if (!strcmp(*arg->argv_optarg, short_name))
  {
    if ((! --arg->argc_optarg) || (! ++arg->argv_optarg))
    {
      fprintf(stderr, "missing option value\n");
      exit(2);
    }

    *pval = atol(*arg->argv_optarg);
  }
  else
  {
    return 1;
  }

  --arg->argc_optarg, ++arg->argv_optarg;    /* next argument */
  return 0;
}

/* returns 0 on successful match */
static int optarg_bool(optarg * arg, int * pval, char * long_name,
                       char * short_name)
{
  if (!strcmp(*arg->argv_optarg, long_name)
      || !strcmp(*arg->argv_optarg, short_name))
  {
    *pval = 1;
  }
  else
  {
    return 1;
  }

  --arg->argc_optarg, ++arg->argv_optarg;    /* next argument */
  return 0;
}

static void optarg_free(optarg * arg)
{
  free(arg);
}

options * options_parse(int argc, char ** argv)
{
  options * opt;
  optarg * arg;
  
  opt = (options *)calloc(1, sizeof(*opt));
  
  /* first set the default values */
  opt->num_samples_opt = OPT_DEFAULT_NUM_SAMPLES;
  opt->burn_in_opt = OPT_DEFAULT_BURNIN;
  opt->interval_opt = OPT_DEFAULT_INTERVAL;
  opt->help_opt = OPT_DEFAULT_HELP;
  opt->lastval_opt = OPT_DEFAULT_LASTVAL;
  opt->randomize_opt = OPT_DEFAULT_RANDOMIZE;


  arg = optarg_alloc(argc, argv);

  while(optarg_more(arg))
  {
    if (optarg_long(arg, &opt->num_samples_opt, OPT_LONG_NUM_SAMPLES,
                    OPT_SHORT_NUM_SAMPLES)
        && optarg_long(arg, &opt->burn_in_opt, OPT_LONG_BURNIN,
                       OPT_SHORT_BURNIN)
        && optarg_long(arg, &opt->interval_opt, OPT_LONG_INTERVAL,
                       OPT_SHORT_INTERVAL)
        && optarg_bool(arg, &opt->lastval_opt, OPT_LONG_LASTVAL,
                       OPT_SHORT_LASTVAL)
        && optarg_bool(arg, &opt->randomize_opt, OPT_LONG_RANDOMIZE,
                       OPT_SHORT_RANDOMIZE)
        && optarg_bool(arg, &opt->help_opt, OPT_LONG_HELP,
                       OPT_SHORT_HELP))
    {
      fprintf(stderr, "unknown option %s, try -h or --help\n",
              *arg->argv_optarg);

      free(opt);
      optarg_free(arg);
      
      exit(1);
    }
    /* check if help is requested */
    if (opt->help_opt)
    {
      printf(options_usage);
      exit(0);
    }
  }

  /* check options */
  if (opt->num_samples_opt < 0)
  {
    fprintf(stderr, "num_samples must be >= 0\n");
    exit(3);
  }
  if ((opt->burn_in_opt < 0) || (opt->burn_in_opt > opt->num_samples_opt))
  {
    fprintf(stderr, "0 <= burnin <= num_samples\n");
    exit(3);
  }

  if (opt->interval_opt == OPT_DEFAULT_INTERVAL)
    opt->interval_opt = opt->num_samples_opt;
  
  if ((opt->interval_opt < 0) || (opt->interval_opt > opt->num_samples_opt))
  {
    fprintf(stderr, "0 <= interval <= num_samples\n");
    exit(3);
  }

  return opt;
}
